﻿using System.Runtime.InteropServices;
using MathS = System.Math;

#if F32
	using Num = System.Single;
	namespace Reign.Core.MathF32
#endif
#if F64
	using Num = System.Double;
	namespace Reign.Core.MathF64
#endif
#if I32
	using Num = System.Int32;
	namespace Reign.Core.MathI32
#endif
{
	[StructLayout(LayoutKind.Sequential)]
	public struct Vector4
	{
		#region Properties
		public Num X, Y, Z, W;
		public const int SizeOf = sizeof(Num) * 4;

		public Num R
		{
			get {return X;}
			set {X = value;}
		}

		public Num G
		{
			get {return Y;}
			set {Y = value;}
		}

		public Num B
		{
			get {return Z;}
			set {Z = value;}
		}

		public Num A
		{
			get {return W;}
			set {W = value;}
		}
		#endregion

		#region Constructors
		public Vector4(Num value)
		{
			X = value;
			Y = value;
			Z = value;
			W = value;
		}

		public Vector4(Num x, Num y, Num z, Num w)
		{
			X = x;
			Y = y;
			Z = z;
			W = w;
		}

		public Vector4(Vector3 xyz, Num w)
		{
			X = xyz.X;
			Y = xyz.Y;
			Z = xyz.Z;
			W = w;
		}
		#endregion

		#region Operators
		// +
		public static Vector4 operator+(Vector4 p1, Vector4 p2) {return new Vector4(p1.X+p2.X, p1.Y+p2.Y, p1.Z+p2.Z, p1.W+p2.W);}
		public static Vector4 operator+(Vector4 p1, Num p2) {return new Vector4(p1.X+p2, p1.Y+p2, p1.Z+p2, p1.W+p2);}
		public static Vector4 operator+(Num p1, Vector4 p2) {return new Vector4(p1+p2.X, p1+p2.Y, p1+p2.Z, p1+p2.W);}
		// -
		public static Vector4 operator-(Vector4 p1, Vector4 p2) {return new Vector4(p1.X-p2.X, p1.Y-p2.Y, p1.Z-p2.Z, p1.W-p2.W);}
		public static Vector4 operator-(Vector4 p1, Num p2) {return new Vector4(p1.X-p2, p1.Y-p2, p1.Z-p2, p1.W-p2);}
		public static Vector4 operator-(Num p1, Vector4 p2) {return new Vector4(p1-p2.X, p1-p2.Y, p1-p2.Z, p1-p2.W);}
		public static Vector4 operator-(Vector4 p1) {return new Vector4(-p1.X, -p1.Y, -p1.Z, p1.W);}
		// *
		public static Vector4 operator*(Vector4 p1, Vector4 p2) {return new Vector4(p1.X*p2.X, p1.Y*p2.Y, p1.Z*p2.Z, p1.W*p2.W);}
		public static Vector4 operator*(Vector4 p1, Num p2) {return new Vector4(p1.X*p2, p1.Y*p2, p1.Z*p2, p1.W*p2);}
		public static Vector4 operator*(Num p1, Vector4 p2) {return new Vector4(p1*p2.X, p1*p2.Y, p1*p2.Z, p1*p2.W);}
		// /
		public static Vector4 operator/(Vector4 p1, Vector4 p2) {return new Vector4(p1.X/p2.X, p1.Y/p2.Y, p1.Z/p2.Z, p1.W/p2.W);}
		public static Vector4 operator/(Vector4 p1, Num p2) {return new Vector4(p1.X/p2, p1.Y/p2, p1.Z/p2, p1.W/p2);}
		public static Vector4 operator/(Num p1, Vector4 p2) {return new Vector4(p1/p2.X, p1/p2.Y, p1/p2.Z, p1/p2.W);}
		// ==
		public static bool operator==(Vector4 p1, Vector4 p2) {return (p1.X==p2.X && p1.Y==p2.Y && p1.Z==p2.Z && p1.W==p2.W);}
		public static bool operator!=(Vector4 p1, Vector4 p2) {return (p1.X!=p2.X || p1.Y!=p2.Y || p1.Z!=p2.Z || p1.W!=p2.W);}
		//Convert
		public Vector2 ToVector2() {return new Vector2(X, Y);}
		public Vector3 ToVector3() {return new Vector3(X, Y, Z);}
		#endregion

		#region Methods
		public override bool Equals(object obj)
		{
			return obj != null && (Vector4)obj == this;
		}

		public override string ToString()
		{
			return string.Format("{0}, {1}, {2}, {3}", X, Y, Z, W);
		}

		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		public Vector4 Abs()
		{
			return new Vector4(MathS.Abs(X), MathS.Abs(Y), MathS.Abs(Z), MathS.Abs(W));
		}

		public Vector4 Pow(Num value)
		{
			return new Vector4((Num)MathS.Pow(X, value), (Num)MathS.Pow(Y, value), (Num)MathS.Pow(Z, value), (Num)MathS.Pow(W, value));
		}

		public Vector4 Floor()
		{
			#if I32
			return this;
			#else
			return new Vector4((Num)MathS.Floor(X), (Num)MathS.Floor(Y), (Num)MathS.Floor(Z), (Num)MathS.Floor(W));
			#endif
		}

		public Vector4 Max(Num value)
		{
			return new Vector4(MathS.Max(X, value), MathS.Max(Y, value), MathS.Max(Z, value), MathS.Max(W, value));
		}

		public Vector4 Min(Num value)
		{
			return new Vector4(MathS.Min(X, value), MathS.Min(Y, value), MathS.Min(Z, value), MathS.Min(W, value));
		}

		public Vector4 Normalize()
		{
			return this * (Math.One / (Num)MathS.Sqrt((X*X) + (Y*Y) + (Z*Z) + (W*W)));
		}

		public Vector4 Transform(Matrix4 matrix)
		{
			return (matrix.X*X) + (matrix.Y*Y) + (matrix.Z*Z) + (matrix.W*W);
		}

		public bool AproxEquals2(Vector4 vector, float tolerance)
		{
			return
				(MathS.Abs(this.X-vector.X) <= tolerance) &&
				(MathS.Abs(this.Y-vector.Y) <= tolerance);
		}

		public bool AproxEquals3(Vector4 vector, float tolerance)
		{
			return
				(MathS.Abs(this.X-vector.X) <= tolerance) &&
				(MathS.Abs(this.Y-vector.Y) <= tolerance) &&
				(MathS.Abs(this.Z-vector.Z) <= tolerance);
		}
		#endregion
	}
}